/*-------------------------------------------------------------------------
 *
 * foreigncmds.c
 *      foreign-data wrapper/server creation/manipulation commands
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *      src/backend/commands/foreigncmds.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/reloptions.h"
#include "access/xact.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
#include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"


typedef struct
{
    char       *tablename;
    char       *cmd;
} import_error_callback_arg;

/* Internal functions */
static void import_error_callback(void *arg);


/*
 * Convert a DefElem list to the text array format that is used in
 * pg_foreign_data_wrapper, pg_foreign_server, pg_user_mapping, and
 * pg_foreign_table.
 *
 * Returns the array in the form of a Datum, or PointerGetDatum(NULL)
 * if the list is empty.
 *
 * Note: The array is usually stored to database without further
 * processing, hence any validation should be done before this
 * conversion.
 */
static Datum
optionListToArray(List *options)
{
    ArrayBuildState *astate = NULL;
    ListCell   *cell;

    foreach(cell, options)
    {
        DefElem    *def = lfirst(cell);
        const char *value;
        Size        len;
        text       *t;

        value = defGetString(def);
        len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value);
        t = palloc(len + 1);
        SET_VARSIZE(t, len);
        sprintf(VARDATA(t), "%s=%s", def->defname, value);

        astate = accumArrayResult(astate, PointerGetDatum(t),
                                  false, TEXTOID,
                                  CurrentMemoryContext);
    }

    if (astate)
        return makeArrayResult(astate, CurrentMemoryContext);

    return PointerGetDatum(NULL);
}


/*
 * Transform a list of DefElem into text array format.  This is substantially
 * the same thing as optionListToArray(), except we recognize SET/ADD/DROP
 * actions for modifying an existing list of options, which is passed in
 * Datum form as oldOptions.  Also, if fdwvalidator isn't InvalidOid
 * it specifies a validator function to call on the result.
 *
 * Returns the array in the form of a Datum, or PointerGetDatum(NULL)
 * if the list is empty.
 *
 * This is used by CREATE/ALTER of FOREIGN DATA WRAPPER/SERVER/USER MAPPING/
 * FOREIGN TABLE.
 */
Datum
transformGenericOptions(Oid catalogId,
                        Datum oldOptions,
                        List *options,
                        Oid fdwvalidator)
{// #lizard forgives
    List       *resultOptions = untransformRelOptions(oldOptions);
    ListCell   *optcell;
    Datum        result;

    foreach(optcell, options)
    {
        DefElem    *od = lfirst(optcell);
        ListCell   *cell;
        ListCell   *prev = NULL;

        /*
         * Find the element in resultOptions.  We need this for validation in
         * all cases.  Also identify the previous element.
         */
        foreach(cell, resultOptions)
        {
            DefElem    *def = lfirst(cell);

            if (strcmp(def->defname, od->defname) == 0)
                break;
            else
                prev = cell;
        }

        /*
         * It is possible to perform multiple SET/DROP actions on the same
         * option.  The standard permits this, as long as the options to be
         * added are unique.  Note that an unspecified action is taken to be
         * ADD.
         */
        switch (od->defaction)
        {
            case DEFELEM_DROP:
                if (!cell)
                    ereport(ERROR,
                            (errcode(ERRCODE_UNDEFINED_OBJECT),
                             errmsg("option \"%s\" not found",
                                    od->defname)));
                resultOptions = list_delete_cell(resultOptions, cell, prev);
                break;

            case DEFELEM_SET:
                if (!cell)
                    ereport(ERROR,
                            (errcode(ERRCODE_UNDEFINED_OBJECT),
                             errmsg("option \"%s\" not found",
                                    od->defname)));
                lfirst(cell) = od;
                break;

            case DEFELEM_ADD:
            case DEFELEM_UNSPEC:
                if (cell)
                    ereport(ERROR,
                            (errcode(ERRCODE_DUPLICATE_OBJECT),
                             errmsg("option \"%s\" provided more than once",
                                    od->defname)));
                resultOptions = lappend(resultOptions, od);
                break;

            default:
                elog(ERROR, "unrecognized action %d on option \"%s\"",
                     (int) od->defaction, od->defname);
                break;
        }
    }

    result = optionListToArray(resultOptions);

    if (OidIsValid(fdwvalidator))
    {
        Datum        valarg = result;

        /*
         * Pass a null options list as an empty array, so that validators
         * don't have to be declared non-strict to handle the case.
         */
        if (DatumGetPointer(valarg) == NULL)
            valarg = PointerGetDatum(construct_empty_array(TEXTOID));
        OidFunctionCall2(fdwvalidator, valarg, ObjectIdGetDatum(catalogId));
    }

    return result;
}


/*
 * Internal workhorse for changing a data wrapper's owner.
 *
 * Allow this only for superusers; also the new owner must be a
 * superuser.
 */
static void
AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
{
    Form_pg_foreign_data_wrapper form;
    Datum        repl_val[Natts_pg_foreign_data_wrapper];
    bool        repl_null[Natts_pg_foreign_data_wrapper];
    bool        repl_repl[Natts_pg_foreign_data_wrapper];
    Acl           *newAcl;
    Datum        aclDatum;
    bool        isNull;

    form = (Form_pg_foreign_data_wrapper) GETSTRUCT(tup);

    /* Must be a superuser to change a FDW owner */
    if (!superuser())
        ereport(ERROR,
                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 errmsg("permission denied to change owner of foreign-data wrapper \"%s\"",
                        NameStr(form->fdwname)),
                 errhint("Must be superuser to change owner of a foreign-data wrapper.")));

    /* New owner must also be a superuser */
    if (!superuser_arg(newOwnerId))
        ereport(ERROR,
                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 errmsg("permission denied to change owner of foreign-data wrapper \"%s\"",
                        NameStr(form->fdwname)),
                 errhint("The owner of a foreign-data wrapper must be a superuser.")));

    if (form->fdwowner != newOwnerId)
    {
        memset(repl_null, false, sizeof(repl_null));
        memset(repl_repl, false, sizeof(repl_repl));

        repl_repl[Anum_pg_foreign_data_wrapper_fdwowner - 1] = true;
        repl_val[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(newOwnerId);

        aclDatum = heap_getattr(tup,
                                Anum_pg_foreign_data_wrapper_fdwacl,
                                RelationGetDescr(rel),
                                &isNull);
        /* Null ACLs do not require changes */
        if (!isNull)
        {
            newAcl = aclnewowner(DatumGetAclP(aclDatum),
                                 form->fdwowner, newOwnerId);
            repl_repl[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
            repl_val[Anum_pg_foreign_data_wrapper_fdwacl - 1] = PointerGetDatum(newAcl);
        }

        tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null,
                                repl_repl);

        CatalogTupleUpdate(rel, &tup->t_self, tup);

        /* Update owner dependency reference */
        changeDependencyOnOwner(ForeignDataWrapperRelationId,
                                HeapTupleGetOid(tup),
                                newOwnerId);
    }

    InvokeObjectPostAlterHook(ForeignDataWrapperRelationId,
                              HeapTupleGetOid(tup), 0);
}

/*
 * Change foreign-data wrapper owner -- by name
 *
 * Note restrictions in the "_internal" function, above.
 */
ObjectAddress
AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId)
{
    Oid            fdwId;
    HeapTuple    tup;
    Relation    rel;
    ObjectAddress address;

    rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);

    tup = SearchSysCacheCopy1(FOREIGNDATAWRAPPERNAME, CStringGetDatum(name));

    if (!HeapTupleIsValid(tup))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                 errmsg("foreign-data wrapper \"%s\" does not exist", name)));

    fdwId = HeapTupleGetOid(tup);

    AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId);

    ObjectAddressSet(address, ForeignDataWrapperRelationId, fdwId);

    heap_freetuple(tup);

    heap_close(rel, RowExclusiveLock);

    return address;
}

/*
 * Change foreign-data wrapper owner -- by OID
 *
 * Note restrictions in the "_internal" function, above.
 */
void
AlterForeignDataWrapperOwner_oid(Oid fwdId, Oid newOwnerId)
{
    HeapTuple    tup;
    Relation    rel;

    rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);

    tup = SearchSysCacheCopy1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fwdId));

    if (!HeapTupleIsValid(tup))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                 errmsg("foreign-data wrapper with OID %u does not exist", fwdId)));

    AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId);

    heap_freetuple(tup);

    heap_close(rel, RowExclusiveLock);
}

/*
 * Internal workhorse for changing a foreign server's owner
 */
static void
AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
{
    Form_pg_foreign_server form;
    Datum        repl_val[Natts_pg_foreign_server];
    bool        repl_null[Natts_pg_foreign_server];
    bool        repl_repl[Natts_pg_foreign_server];
    Acl           *newAcl;
    Datum        aclDatum;
    bool        isNull;

    form = (Form_pg_foreign_server) GETSTRUCT(tup);

    if (form->srvowner != newOwnerId)
    {
        /* Superusers can always do it */
        if (!superuser())
        {
            Oid            srvId;
            AclResult    aclresult;

            srvId = HeapTupleGetOid(tup);

            /* Must be owner */
            if (!pg_foreign_server_ownercheck(srvId, GetUserId()))
                aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
                               NameStr(form->srvname));

            /* Must be able to become new owner */
            check_is_member_of_role(GetUserId(), newOwnerId);

            /* New owner must have USAGE privilege on foreign-data wrapper */
            aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ACL_USAGE);
            if (aclresult != ACLCHECK_OK)
            {
                ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw);

                aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname);
            }
        }

        memset(repl_null, false, sizeof(repl_null));
        memset(repl_repl, false, sizeof(repl_repl));

        repl_repl[Anum_pg_foreign_server_srvowner - 1] = true;
        repl_val[Anum_pg_foreign_server_srvowner - 1] = ObjectIdGetDatum(newOwnerId);

        aclDatum = heap_getattr(tup,
                                Anum_pg_foreign_server_srvacl,
                                RelationGetDescr(rel),
                                &isNull);
        /* Null ACLs do not require changes */
        if (!isNull)
        {
            newAcl = aclnewowner(DatumGetAclP(aclDatum),
                                 form->srvowner, newOwnerId);
            repl_repl[Anum_pg_foreign_server_srvacl - 1] = true;
            repl_val[Anum_pg_foreign_server_srvacl - 1] = PointerGetDatum(newAcl);
        }

        tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null,
                                repl_repl);

        CatalogTupleUpdate(rel, &tup->t_self, tup);

        /* Update owner dependency reference */
        changeDependencyOnOwner(ForeignServerRelationId, HeapTupleGetOid(tup),
                                newOwnerId);
    }

    InvokeObjectPostAlterHook(ForeignServerRelationId,
                              HeapTupleGetOid(tup), 0);
}

/*
 * Change foreign server owner -- by name
 */
ObjectAddress
AlterForeignServerOwner(const char *name, Oid newOwnerId)
{
    Oid            servOid;
    HeapTuple    tup;
    Relation    rel;
    ObjectAddress address;

    rel = heap_open(ForeignServerRelationId, RowExclusiveLock);

    tup = SearchSysCacheCopy1(FOREIGNSERVERNAME, CStringGetDatum(name));

    if (!HeapTupleIsValid(tup))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                 errmsg("server \"%s\" does not exist", name)));

    servOid = HeapTupleGetOid(tup);

    AlterForeignServerOwner_internal(rel, tup, newOwnerId);

    ObjectAddressSet(address, ForeignServerRelationId, servOid);

    heap_freetuple(tup);

    heap_close(rel, RowExclusiveLock);

    return address;
}

/*
 * Change foreign server owner -- by OID
 */
void
AlterForeignServerOwner_oid(Oid srvId, Oid newOwnerId)
{
    HeapTuple    tup;
    Relation    rel;

    rel = heap_open(ForeignServerRelationId, RowExclusiveLock);

    tup = SearchSysCacheCopy1(FOREIGNSERVEROID, ObjectIdGetDatum(srvId));

    if (!HeapTupleIsValid(tup))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                 errmsg("foreign server with OID %u does not exist", srvId)));

    AlterForeignServerOwner_internal(rel, tup, newOwnerId);

    heap_freetuple(tup);

    heap_close(rel, RowExclusiveLock);
}

/*
 * Convert a handler function name passed from the parser to an Oid.
 */
static Oid
lookup_fdw_handler_func(DefElem *handler)
{
    Oid            handlerOid;
    Oid            funcargtypes[1];    /* dummy */

    if (handler == NULL || handler->arg == NULL)
        return InvalidOid;

    /* handlers have no arguments */
    handlerOid = LookupFuncName((List *) handler->arg, 0, funcargtypes, false);

    /* check that handler has correct return type */
    if (get_func_rettype(handlerOid) != FDW_HANDLEROID)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                 errmsg("function %s must return type %s",
                        NameListToString((List *) handler->arg), "fdw_handler")));

    return handlerOid;
}

/*
 * Convert a validator function name passed from the parser to an Oid.
 */
static Oid
lookup_fdw_validator_func(DefElem *validator)
{
    Oid            funcargtypes[2];

    if (validator == NULL || validator->arg == NULL)
        return InvalidOid;

    /* validators take text[], oid */
    funcargtypes[0] = TEXTARRAYOID;
    funcargtypes[1] = OIDOID;

    return LookupFuncName((List *) validator->arg, 2, funcargtypes, false);
    /* validator's return value is ignored, so we don't check the type */
}

/*
 * Process function options of CREATE/ALTER FDW
 */
static void
parse_func_options(List *func_options,
                   bool *handler_given, Oid *fdwhandler,
                   bool *validator_given, Oid *fdwvalidator)
{
    ListCell   *cell;

    *handler_given = false;
    *validator_given = false;
    /* return InvalidOid if not given */
    *fdwhandler = InvalidOid;
    *fdwvalidator = InvalidOid;

    foreach(cell, func_options)
    {
        DefElem    *def = (DefElem *) lfirst(cell);

        if (strcmp(def->defname, "handler") == 0)
        {
            if (*handler_given)
                ereport(ERROR,
                        (errcode(ERRCODE_SYNTAX_ERROR),
                         errmsg("conflicting or redundant options")));
            *handler_given = true;
            *fdwhandler = lookup_fdw_handler_func(def);
        }
        else if (strcmp(def->defname, "validator") == 0)
        {
            if (*validator_given)
                ereport(ERROR,
                        (errcode(ERRCODE_SYNTAX_ERROR),
                         errmsg("conflicting or redundant options")));
            *validator_given = true;
            *fdwvalidator = lookup_fdw_validator_func(def);
        }
        else
            elog(ERROR, "option \"%s\" not recognized",
                 def->defname);
    }
}

/*
 * Create a foreign-data wrapper
 */
ObjectAddress
CreateForeignDataWrapper(CreateFdwStmt *stmt)
{
    Relation    rel;
    Datum        values[Natts_pg_foreign_data_wrapper];
    bool        nulls[Natts_pg_foreign_data_wrapper];
    HeapTuple    tuple;
    Oid            fdwId;
    bool        handler_given;
    bool        validator_given;
    Oid            fdwhandler;
    Oid            fdwvalidator;
    Datum        fdwoptions;
    Oid            ownerId;
    ObjectAddress myself;
    ObjectAddress referenced;

    rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);

    /* Must be super user */
    if (!superuser())
        ereport(ERROR,
                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 errmsg("permission denied to create foreign-data wrapper \"%s\"",
                        stmt->fdwname),
                 errhint("Must be superuser to create a foreign-data wrapper.")));

    /* For now the owner cannot be specified on create. Use effective user ID. */
    ownerId = GetUserId();

    /*
     * Check that there is no other foreign-data wrapper by this name.
     */
    if (GetForeignDataWrapperByName(stmt->fdwname, true) != NULL)
        ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_OBJECT),
                 errmsg("foreign-data wrapper \"%s\" already exists",
                        stmt->fdwname)));

    /*
     * Insert tuple into pg_foreign_data_wrapper.
     */
    memset(values, 0, sizeof(values));
    memset(nulls, false, sizeof(nulls));

    values[Anum_pg_foreign_data_wrapper_fdwname - 1] =
        DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname));
    values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId);

    /* Lookup handler and validator functions, if given */
    parse_func_options(stmt->func_options,
                       &handler_given, &fdwhandler,
                       &validator_given, &fdwvalidator);

    values[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler);
    values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator);

    nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;

    fdwoptions = transformGenericOptions(ForeignDataWrapperRelationId,
                                         PointerGetDatum(NULL),
                                         stmt->options,
                                         fdwvalidator);

    if (PointerIsValid(DatumGetPointer(fdwoptions)))
        values[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = fdwoptions;
    else
        nulls[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true;

    tuple = heap_form_tuple(rel->rd_att, values, nulls);

    fdwId = CatalogTupleInsert(rel, tuple);

    heap_freetuple(tuple);

    /* record dependencies */
    myself.classId = ForeignDataWrapperRelationId;
    myself.objectId = fdwId;
    myself.objectSubId = 0;

    if (OidIsValid(fdwhandler))
    {
        referenced.classId = ProcedureRelationId;
        referenced.objectId = fdwhandler;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }

    if (OidIsValid(fdwvalidator))
    {
        referenced.classId = ProcedureRelationId;
        referenced.objectId = fdwvalidator;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }

    recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId);

    /* dependency on extension */
    recordDependencyOnCurrentExtension(&myself, false);

    /* Post creation hook for new foreign data wrapper */
    InvokeObjectPostCreateHook(ForeignDataWrapperRelationId, fdwId, 0);

    heap_close(rel, RowExclusiveLock);

    return myself;
}


/*
 * Alter foreign-data wrapper
 */
ObjectAddress
AlterForeignDataWrapper(AlterFdwStmt *stmt)
{// #lizard forgives
    Relation    rel;
    HeapTuple    tp;
    Form_pg_foreign_data_wrapper fdwForm;
    Datum        repl_val[Natts_pg_foreign_data_wrapper];
    bool        repl_null[Natts_pg_foreign_data_wrapper];
    bool        repl_repl[Natts_pg_foreign_data_wrapper];
    Oid            fdwId;
    bool        isnull;
    Datum        datum;
    bool        handler_given;
    bool        validator_given;
    Oid            fdwhandler;
    Oid            fdwvalidator;
    ObjectAddress myself;

    rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);

    /* Must be super user */
    if (!superuser())
        ereport(ERROR,
                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 errmsg("permission denied to alter foreign-data wrapper \"%s\"",
                        stmt->fdwname),
                 errhint("Must be superuser to alter a foreign-data wrapper.")));

    tp = SearchSysCacheCopy1(FOREIGNDATAWRAPPERNAME,
                             CStringGetDatum(stmt->fdwname));

    if (!HeapTupleIsValid(tp))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                 errmsg("foreign-data wrapper \"%s\" does not exist", stmt->fdwname)));

    fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
    fdwId = HeapTupleGetOid(tp);

    memset(repl_val, 0, sizeof(repl_val));
    memset(repl_null, false, sizeof(repl_null));
    memset(repl_repl, false, sizeof(repl_repl));

    parse_func_options(stmt->func_options,
                       &handler_given, &fdwhandler,
                       &validator_given, &fdwvalidator);

    if (handler_given)
    {
        repl_val[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler);
        repl_repl[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = true;

        /*
         * It could be that the behavior of accessing foreign table changes
         * with the new handler.  Warn about this.
         */
        ereport(WARNING,
                (errmsg("changing the foreign-data wrapper handler can change behavior of existing foreign tables")));
    }

    if (validator_given)
    {
        repl_val[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator);
        repl_repl[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = true;

        /*
         * It could be that existing options for the FDW or dependent SERVER,
         * USER MAPPING or FOREIGN TABLE objects are no longer valid according
         * to the new validator.  Warn about this.
         */
        if (OidIsValid(fdwvalidator))
            ereport(WARNING,
                    (errmsg("changing the foreign-data wrapper validator can cause "
                            "the options for dependent objects to become invalid")));
    }
    else
    {
        /*
         * Validator is not changed, but we need it for validating options.
         */
        fdwvalidator = fdwForm->fdwvalidator;
    }

    /*
     * If options specified, validate and update.
     */
    if (stmt->options)
    {
        /* Extract the current options */
        datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
                                tp,
                                Anum_pg_foreign_data_wrapper_fdwoptions,
                                &isnull);
        if (isnull)
            datum = PointerGetDatum(NULL);

        /* Transform the options */
        datum = transformGenericOptions(ForeignDataWrapperRelationId,
                                        datum,
                                        stmt->options,
                                        fdwvalidator);

        if (PointerIsValid(DatumGetPointer(datum)))
            repl_val[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = datum;
        else
            repl_null[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true;

        repl_repl[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true;
    }

    /* Everything looks good - update the tuple */
    tp = heap_modify_tuple(tp, RelationGetDescr(rel),
                           repl_val, repl_null, repl_repl);

    CatalogTupleUpdate(rel, &tp->t_self, tp);

    heap_freetuple(tp);

    ObjectAddressSet(myself, ForeignDataWrapperRelationId, fdwId);

    /* Update function dependencies if we changed them */
    if (handler_given || validator_given)
    {
        ObjectAddress referenced;

        /*
         * Flush all existing dependency records of this FDW on functions; we
         * assume there can be none other than the ones we are fixing.
         */
        deleteDependencyRecordsForClass(ForeignDataWrapperRelationId,
                                        fdwId,
                                        ProcedureRelationId,
                                        DEPENDENCY_NORMAL);

        /* And build new ones. */

        if (OidIsValid(fdwhandler))
        {
            referenced.classId = ProcedureRelationId;
            referenced.objectId = fdwhandler;
            referenced.objectSubId = 0;
            recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
        }

        if (OidIsValid(fdwvalidator))
        {
            referenced.classId = ProcedureRelationId;
            referenced.objectId = fdwvalidator;
            referenced.objectSubId = 0;
            recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
        }
    }

    InvokeObjectPostAlterHook(ForeignDataWrapperRelationId, fdwId, 0);

    heap_close(rel, RowExclusiveLock);

    return myself;
}


/*
 * Drop foreign-data wrapper by OID
 */
void
RemoveForeignDataWrapperById(Oid fdwId)
{
    HeapTuple    tp;
    Relation    rel;

    rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);

    tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwId));

    if (!HeapTupleIsValid(tp))
        elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwId);

    CatalogTupleDelete(rel, &tp->t_self);

    ReleaseSysCache(tp);

    heap_close(rel, RowExclusiveLock);
}


/*
 * Create a foreign server
 */
ObjectAddress
CreateForeignServer(CreateForeignServerStmt *stmt)
{
    Relation    rel;
    Datum        srvoptions;
    Datum        values[Natts_pg_foreign_server];
    bool        nulls[Natts_pg_foreign_server];
    HeapTuple    tuple;
    Oid            srvId;
    Oid            ownerId;
    AclResult    aclresult;
    ObjectAddress myself;
    ObjectAddress referenced;
    ForeignDataWrapper *fdw;

    rel = heap_open(ForeignServerRelationId, RowExclusiveLock);

    /* For now the owner cannot be specified on create. Use effective user ID. */
    ownerId = GetUserId();

    /*
     * Check that there is no other foreign server by this name. Do nothing if
     * IF NOT EXISTS was enforced.
     */
    if (GetForeignServerByName(stmt->servername, true) != NULL)
    {
        if (stmt->if_not_exists)
        {
            ereport(NOTICE,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                     errmsg("server \"%s\" already exists, skipping",
                            stmt->servername)));
            heap_close(rel, RowExclusiveLock);
            return InvalidObjectAddress;
        }
        else
            ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                     errmsg("server \"%s\" already exists",
                            stmt->servername)));
    }

    /*
     * Check that the FDW exists and that we have USAGE on it. Also get the
     * actual FDW for option validation etc.
     */
    fdw = GetForeignDataWrapperByName(stmt->fdwname, false);

    aclresult = pg_foreign_data_wrapper_aclcheck(fdw->fdwid, ownerId, ACL_USAGE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname);

    /*
     * Insert tuple into pg_foreign_server.
     */
    memset(values, 0, sizeof(values));
    memset(nulls, false, sizeof(nulls));

    values[Anum_pg_foreign_server_srvname - 1] =
        DirectFunctionCall1(namein, CStringGetDatum(stmt->servername));
    values[Anum_pg_foreign_server_srvowner - 1] = ObjectIdGetDatum(ownerId);
    values[Anum_pg_foreign_server_srvfdw - 1] = ObjectIdGetDatum(fdw->fdwid);

    /* Add server type if supplied */
    if (stmt->servertype)
        values[Anum_pg_foreign_server_srvtype - 1] =
            CStringGetTextDatum(stmt->servertype);
    else
        nulls[Anum_pg_foreign_server_srvtype - 1] = true;

    /* Add server version if supplied */
    if (stmt->version)
        values[Anum_pg_foreign_server_srvversion - 1] =
            CStringGetTextDatum(stmt->version);
    else
        nulls[Anum_pg_foreign_server_srvversion - 1] = true;

    /* Start with a blank acl */
    nulls[Anum_pg_foreign_server_srvacl - 1] = true;

    /* Add server options */
    srvoptions = transformGenericOptions(ForeignServerRelationId,
                                         PointerGetDatum(NULL),
                                         stmt->options,
                                         fdw->fdwvalidator);

    if (PointerIsValid(DatumGetPointer(srvoptions)))
        values[Anum_pg_foreign_server_srvoptions - 1] = srvoptions;
    else
        nulls[Anum_pg_foreign_server_srvoptions - 1] = true;

    tuple = heap_form_tuple(rel->rd_att, values, nulls);

    srvId = CatalogTupleInsert(rel, tuple);

    heap_freetuple(tuple);

    /* record dependencies */
    myself.classId = ForeignServerRelationId;
    myself.objectId = srvId;
    myself.objectSubId = 0;

    referenced.classId = ForeignDataWrapperRelationId;
    referenced.objectId = fdw->fdwid;
    referenced.objectSubId = 0;
    recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);

    recordDependencyOnOwner(ForeignServerRelationId, srvId, ownerId);

    /* dependency on extension */
    recordDependencyOnCurrentExtension(&myself, false);

    /* Post creation hook for new foreign server */
    InvokeObjectPostCreateHook(ForeignServerRelationId, srvId, 0);

    heap_close(rel, RowExclusiveLock);

    return myself;
}


/*
 * Alter foreign server
 */
ObjectAddress
AlterForeignServer(AlterForeignServerStmt *stmt)
{// #lizard forgives
    Relation    rel;
    HeapTuple    tp;
    Datum        repl_val[Natts_pg_foreign_server];
    bool        repl_null[Natts_pg_foreign_server];
    bool        repl_repl[Natts_pg_foreign_server];
    Oid            srvId;
    Form_pg_foreign_server srvForm;
    ObjectAddress address;

    rel = heap_open(ForeignServerRelationId, RowExclusiveLock);

    tp = SearchSysCacheCopy1(FOREIGNSERVERNAME,
                             CStringGetDatum(stmt->servername));

    if (!HeapTupleIsValid(tp))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                 errmsg("server \"%s\" does not exist", stmt->servername)));

    srvId = HeapTupleGetOid(tp);
    srvForm = (Form_pg_foreign_server) GETSTRUCT(tp);

    /*
     * Only owner or a superuser can ALTER a SERVER.
     */
    if (!pg_foreign_server_ownercheck(srvId, GetUserId()))
        aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
                       stmt->servername);

    memset(repl_val, 0, sizeof(repl_val));
    memset(repl_null, false, sizeof(repl_null));
    memset(repl_repl, false, sizeof(repl_repl));

    if (stmt->has_version)
    {
        /*
         * Change the server VERSION string.
         */
        if (stmt->version)
            repl_val[Anum_pg_foreign_server_srvversion - 1] =
                CStringGetTextDatum(stmt->version);
        else
            repl_null[Anum_pg_foreign_server_srvversion - 1] = true;

        repl_repl[Anum_pg_foreign_server_srvversion - 1] = true;
    }

    if (stmt->options)
    {
        ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw);
        Datum        datum;
        bool        isnull;

        /* Extract the current srvoptions */
        datum = SysCacheGetAttr(FOREIGNSERVEROID,
                                tp,
                                Anum_pg_foreign_server_srvoptions,
                                &isnull);
        if (isnull)
            datum = PointerGetDatum(NULL);

        /* Prepare the options array */
        datum = transformGenericOptions(ForeignServerRelationId,
                                        datum,
                                        stmt->options,
                                        fdw->fdwvalidator);

        if (PointerIsValid(DatumGetPointer(datum)))
            repl_val[Anum_pg_foreign_server_srvoptions - 1] = datum;
        else
            repl_null[Anum_pg_foreign_server_srvoptions - 1] = true;

        repl_repl[Anum_pg_foreign_server_srvoptions - 1] = true;
    }

    /* Everything looks good - update the tuple */
    tp = heap_modify_tuple(tp, RelationGetDescr(rel),
                           repl_val, repl_null, repl_repl);

    CatalogTupleUpdate(rel, &tp->t_self, tp);

    InvokeObjectPostAlterHook(ForeignServerRelationId, srvId, 0);

    ObjectAddressSet(address, ForeignServerRelationId, srvId);

    heap_freetuple(tp);

    heap_close(rel, RowExclusiveLock);

    return address;
}


/*
 * Drop foreign server by OID
 */
void
RemoveForeignServerById(Oid srvId)
{
    HeapTuple    tp;
    Relation    rel;

    rel = heap_open(ForeignServerRelationId, RowExclusiveLock);

    tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(srvId));

    if (!HeapTupleIsValid(tp))
        elog(ERROR, "cache lookup failed for foreign server %u", srvId);

    CatalogTupleDelete(rel, &tp->t_self);

    ReleaseSysCache(tp);

    heap_close(rel, RowExclusiveLock);
}


/*
 * Common routine to check permission for user-mapping-related DDL
 * commands.  We allow server owners to operate on any mapping, and
 * users to operate on their own mapping.
 */
static void
user_mapping_ddl_aclcheck(Oid umuserid, Oid serverid, const char *servername)
{
    Oid            curuserid = GetUserId();

    if (!pg_foreign_server_ownercheck(serverid, curuserid))
    {
        if (umuserid == curuserid)
        {
            AclResult    aclresult;

            aclresult = pg_foreign_server_aclcheck(serverid, curuserid, ACL_USAGE);
            if (aclresult != ACLCHECK_OK)
                aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, servername);
        }
        else
            aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
                           servername);
    }
}


/*
 * Create user mapping
 */
ObjectAddress
CreateUserMapping(CreateUserMappingStmt *stmt)
{
    Relation    rel;
    Datum        useoptions;
    Datum        values[Natts_pg_user_mapping];
    bool        nulls[Natts_pg_user_mapping];
    HeapTuple    tuple;
    Oid            useId;
    Oid            umId;
    ObjectAddress myself;
    ObjectAddress referenced;
    ForeignServer *srv;
    ForeignDataWrapper *fdw;
    RoleSpec   *role = (RoleSpec *) stmt->user;

    rel = heap_open(UserMappingRelationId, RowExclusiveLock);

    if (role->roletype == ROLESPEC_PUBLIC)
        useId = ACL_ID_PUBLIC;
    else
        useId = get_rolespec_oid(stmt->user, false);

    /* Check that the server exists. */
    srv = GetForeignServerByName(stmt->servername, false);

    user_mapping_ddl_aclcheck(useId, srv->serverid, stmt->servername);

    /*
     * Check that the user mapping is unique within server.
     */
    umId = GetSysCacheOid2(USERMAPPINGUSERSERVER,
                           ObjectIdGetDatum(useId),
                           ObjectIdGetDatum(srv->serverid));

    if (OidIsValid(umId))
    {
        if (stmt->if_not_exists)
        {
            ereport(NOTICE,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                     errmsg("user mapping for \"%s\" already exists for server %s, skipping",
                            MappingUserName(useId),
                            stmt->servername)));

            heap_close(rel, RowExclusiveLock);
            return InvalidObjectAddress;
        }
        else
            ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                     errmsg("user mapping for \"%s\" already exists for server %s",
                            MappingUserName(useId),
                            stmt->servername)));
    }

    fdw = GetForeignDataWrapper(srv->fdwid);

    /*
     * Insert tuple into pg_user_mapping.
     */
    memset(values, 0, sizeof(values));
    memset(nulls, false, sizeof(nulls));

    values[Anum_pg_user_mapping_umuser - 1] = ObjectIdGetDatum(useId);
    values[Anum_pg_user_mapping_umserver - 1] = ObjectIdGetDatum(srv->serverid);

    /* Add user options */
    useoptions = transformGenericOptions(UserMappingRelationId,
                                         PointerGetDatum(NULL),
                                         stmt->options,
                                         fdw->fdwvalidator);

    if (PointerIsValid(DatumGetPointer(useoptions)))
        values[Anum_pg_user_mapping_umoptions - 1] = useoptions;
    else
        nulls[Anum_pg_user_mapping_umoptions - 1] = true;

    tuple = heap_form_tuple(rel->rd_att, values, nulls);

    umId = CatalogTupleInsert(rel, tuple);

    heap_freetuple(tuple);

    /* Add dependency on the server */
    myself.classId = UserMappingRelationId;
    myself.objectId = umId;
    myself.objectSubId = 0;

    referenced.classId = ForeignServerRelationId;
    referenced.objectId = srv->serverid;
    referenced.objectSubId = 0;
    recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);

    if (OidIsValid(useId))
    {
        /* Record the mapped user dependency */
        recordDependencyOnOwner(UserMappingRelationId, umId, useId);
    }

    /* dependency on extension */
    recordDependencyOnCurrentExtension(&myself, false);

    /* Post creation hook for new user mapping */
    InvokeObjectPostCreateHook(UserMappingRelationId, umId, 0);

    heap_close(rel, RowExclusiveLock);

    return myself;
}


/*
 * Alter user mapping
 */
ObjectAddress
AlterUserMapping(AlterUserMappingStmt *stmt)
{
    Relation    rel;
    HeapTuple    tp;
    Datum        repl_val[Natts_pg_user_mapping];
    bool        repl_null[Natts_pg_user_mapping];
    bool        repl_repl[Natts_pg_user_mapping];
    Oid            useId;
    Oid            umId;
    ForeignServer *srv;
    ObjectAddress address;
    RoleSpec   *role = (RoleSpec *) stmt->user;

    rel = heap_open(UserMappingRelationId, RowExclusiveLock);

    if (role->roletype == ROLESPEC_PUBLIC)
        useId = ACL_ID_PUBLIC;
    else
        useId = get_rolespec_oid(stmt->user, false);

    srv = GetForeignServerByName(stmt->servername, false);

    umId = GetSysCacheOid2(USERMAPPINGUSERSERVER,
                           ObjectIdGetDatum(useId),
                           ObjectIdGetDatum(srv->serverid));
    if (!OidIsValid(umId))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                 errmsg("user mapping for \"%s\" does not exist for the server",
                        MappingUserName(useId))));

    user_mapping_ddl_aclcheck(useId, srv->serverid, stmt->servername);

    tp = SearchSysCacheCopy1(USERMAPPINGOID, ObjectIdGetDatum(umId));

    if (!HeapTupleIsValid(tp))
        elog(ERROR, "cache lookup failed for user mapping %u", umId);

    memset(repl_val, 0, sizeof(repl_val));
    memset(repl_null, false, sizeof(repl_null));
    memset(repl_repl, false, sizeof(repl_repl));

    if (stmt->options)
    {
        ForeignDataWrapper *fdw;
        Datum        datum;
        bool        isnull;

        /*
         * Process the options.
         */

        fdw = GetForeignDataWrapper(srv->fdwid);

        datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
                                tp,
                                Anum_pg_user_mapping_umoptions,
                                &isnull);
        if (isnull)
            datum = PointerGetDatum(NULL);

        /* Prepare the options array */
        datum = transformGenericOptions(UserMappingRelationId,
                                        datum,
                                        stmt->options,
                                        fdw->fdwvalidator);

        if (PointerIsValid(DatumGetPointer(datum)))
            repl_val[Anum_pg_user_mapping_umoptions - 1] = datum;
        else
            repl_null[Anum_pg_user_mapping_umoptions - 1] = true;

        repl_repl[Anum_pg_user_mapping_umoptions - 1] = true;
    }

    /* Everything looks good - update the tuple */
    tp = heap_modify_tuple(tp, RelationGetDescr(rel),
                           repl_val, repl_null, repl_repl);

    CatalogTupleUpdate(rel, &tp->t_self, tp);

    ObjectAddressSet(address, UserMappingRelationId, umId);

    heap_freetuple(tp);

    heap_close(rel, RowExclusiveLock);

    return address;
}


/*
 * Drop user mapping
 */
Oid
RemoveUserMapping(DropUserMappingStmt *stmt)
{
    ObjectAddress object;
    Oid            useId;
    Oid            umId;
    ForeignServer *srv;
    RoleSpec   *role = (RoleSpec *) stmt->user;

    if (role->roletype == ROLESPEC_PUBLIC)
        useId = ACL_ID_PUBLIC;
    else
    {
        useId = get_rolespec_oid(stmt->user, stmt->missing_ok);
        if (!OidIsValid(useId))
        {
            /*
             * IF EXISTS specified, role not found and not public. Notice this
             * and leave.
             */
            elog(NOTICE, "role \"%s\" does not exist, skipping",
                 role->rolename);
            return InvalidOid;
        }
    }

    srv = GetForeignServerByName(stmt->servername, true);

    if (!srv)
    {
        if (!stmt->missing_ok)
            ereport(ERROR,
                    (errcode(ERRCODE_UNDEFINED_OBJECT),
                     errmsg("server \"%s\" does not exist",
                            stmt->servername)));
        /* IF EXISTS, just note it */
        ereport(NOTICE, (errmsg("server does not exist, skipping")));
        return InvalidOid;
    }

    umId = GetSysCacheOid2(USERMAPPINGUSERSERVER,
                           ObjectIdGetDatum(useId),
                           ObjectIdGetDatum(srv->serverid));

    if (!OidIsValid(umId))
    {
        if (!stmt->missing_ok)
            ereport(ERROR,
                    (errcode(ERRCODE_UNDEFINED_OBJECT),
                     errmsg("user mapping for \"%s\" does not exist for the server",
                            MappingUserName(useId))));

        /* IF EXISTS specified, just note it */
        ereport(NOTICE,
                (errmsg("user mapping for \"%s\" does not exist for the server, skipping",
                        MappingUserName(useId))));
        return InvalidOid;
    }

    user_mapping_ddl_aclcheck(useId, srv->serverid, srv->servername);

    /*
     * Do the deletion
     */
    object.classId = UserMappingRelationId;
    object.objectId = umId;
    object.objectSubId = 0;

    performDeletion(&object, DROP_CASCADE, 0);

    return umId;
}


/*
 * Drop user mapping by OID.  This is called to clean up dependencies.
 */
void
RemoveUserMappingById(Oid umId)
{
    HeapTuple    tp;
    Relation    rel;

    rel = heap_open(UserMappingRelationId, RowExclusiveLock);

    tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(umId));

    if (!HeapTupleIsValid(tp))
        elog(ERROR, "cache lookup failed for user mapping %u", umId);

    CatalogTupleDelete(rel, &tp->t_self);

    ReleaseSysCache(tp);

    heap_close(rel, RowExclusiveLock);
}

/*
 * Create a foreign table
 * call after DefineRelation().
 */
void
CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
{
    Relation    ftrel;
    Datum        ftoptions;
    Datum        values[Natts_pg_foreign_table];
    bool        nulls[Natts_pg_foreign_table];
    HeapTuple    tuple;
    AclResult    aclresult;
    ObjectAddress myself;
    ObjectAddress referenced;
    Oid            ownerId;
    ForeignDataWrapper *fdw;
    ForeignServer *server;

    /*
     * Advance command counter to ensure the pg_attribute tuple is visible;
     * the tuple might be updated to add constraints in previous step.
     */
    CommandCounterIncrement();

    ftrel = heap_open(ForeignTableRelationId, RowExclusiveLock);

    /*
     * For now the owner cannot be specified on create. Use effective user ID.
     */
    ownerId = GetUserId();

    /*
     * Check that the foreign server exists and that we have USAGE on it. Also
     * get the actual FDW for option validation etc.
     */
    server = GetForeignServerByName(stmt->servername, false);
    aclresult = pg_foreign_server_aclcheck(server->serverid, ownerId, ACL_USAGE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, server->servername);

    fdw = GetForeignDataWrapper(server->fdwid);

    /*
     * Insert tuple into pg_foreign_table.
     */
    memset(values, 0, sizeof(values));
    memset(nulls, false, sizeof(nulls));

    values[Anum_pg_foreign_table_ftrelid - 1] = ObjectIdGetDatum(relid);
    values[Anum_pg_foreign_table_ftserver - 1] = ObjectIdGetDatum(server->serverid);
    /* Add table generic options */
    ftoptions = transformGenericOptions(ForeignTableRelationId,
                                        PointerGetDatum(NULL),
                                        stmt->options,
                                        fdw->fdwvalidator);

    if (PointerIsValid(DatumGetPointer(ftoptions)))
        values[Anum_pg_foreign_table_ftoptions - 1] = ftoptions;
    else
        nulls[Anum_pg_foreign_table_ftoptions - 1] = true;

    tuple = heap_form_tuple(ftrel->rd_att, values, nulls);

    CatalogTupleInsert(ftrel, tuple);

    heap_freetuple(tuple);

    /* Add pg_class dependency on the server */
    myself.classId = RelationRelationId;
    myself.objectId = relid;
    myself.objectSubId = 0;

    referenced.classId = ForeignServerRelationId;
    referenced.objectId = server->serverid;
    referenced.objectSubId = 0;
    recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);

    heap_close(ftrel, RowExclusiveLock);
}

/*
 * Import a foreign schema
 */
void
ImportForeignSchema(ImportForeignSchemaStmt *stmt)
{
    ForeignServer *server;
    ForeignDataWrapper *fdw;
    FdwRoutine *fdw_routine;
    AclResult    aclresult;
    List       *cmd_list;
    ListCell   *lc;

    /* Check that the foreign server exists and that we have USAGE on it */
    server = GetForeignServerByName(stmt->server_name, false);
    aclresult = pg_foreign_server_aclcheck(server->serverid, GetUserId(), ACL_USAGE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, server->servername);

    /* Check that the schema exists and we have CREATE permissions on it */
    (void) LookupCreationNamespace(stmt->local_schema);

    /* Get the FDW and check it supports IMPORT */
    fdw = GetForeignDataWrapper(server->fdwid);
    if (!OidIsValid(fdw->fdwhandler))
        ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                 errmsg("foreign-data wrapper \"%s\" has no handler",
                        fdw->fdwname)));
    fdw_routine = GetFdwRoutine(fdw->fdwhandler);
    if (fdw_routine->ImportForeignSchema == NULL)
        ereport(ERROR,
                (errcode(ERRCODE_FDW_NO_SCHEMAS),
                 errmsg("foreign-data wrapper \"%s\" does not support IMPORT FOREIGN SCHEMA",
                        fdw->fdwname)));

    /* Call FDW to get a list of commands */
    cmd_list = fdw_routine->ImportForeignSchema(stmt, server->serverid);

    /* Parse and execute each command */
    foreach(lc, cmd_list)
    {
        char       *cmd = (char *) lfirst(lc);
        import_error_callback_arg callback_arg;
        ErrorContextCallback sqlerrcontext;
        List       *raw_parsetree_list;
        ListCell   *lc2;

        /*
         * Setup error traceback support for ereport().  This is so that any
         * error in the generated SQL will be displayed nicely.
         */
        callback_arg.tablename = NULL;    /* not known yet */
        callback_arg.cmd = cmd;
        sqlerrcontext.callback = import_error_callback;
        sqlerrcontext.arg = (void *) &callback_arg;
        sqlerrcontext.previous = error_context_stack;
        error_context_stack = &sqlerrcontext;

        /*
         * Parse the SQL string into a list of raw parse trees.
         */
        raw_parsetree_list = pg_parse_query(cmd);

        /*
         * Process each parse tree (we allow the FDW to put more than one
         * command per string, though this isn't really advised).
         */
        foreach(lc2, raw_parsetree_list)
        {
            RawStmt    *rs = lfirst_node(RawStmt, lc2);
            CreateForeignTableStmt *cstmt = (CreateForeignTableStmt *) rs->stmt;
            PlannedStmt *pstmt;

            /*
             * Because we only allow CreateForeignTableStmt, we can skip parse
             * analysis, rewrite, and planning steps here.
             */
            if (!IsA(cstmt, CreateForeignTableStmt))
                elog(ERROR,
                     "foreign-data wrapper \"%s\" returned incorrect statement type %d",
                     fdw->fdwname, (int) nodeTag(cstmt));

            /* Ignore commands for tables excluded by filter options */
            if (!IsImportableForeignTable(cstmt->base.relation->relname, stmt))
                continue;

            /* Enable reporting of current table's name on error */
            callback_arg.tablename = cstmt->base.relation->relname;

            /* Ensure creation schema is the one given in IMPORT statement */
            cstmt->base.relation->schemaname = pstrdup(stmt->local_schema);

            /* No planning needed, just make a wrapper PlannedStmt */
            pstmt = makeNode(PlannedStmt);
            pstmt->commandType = CMD_UTILITY;
            pstmt->canSetTag = false;
            pstmt->utilityStmt = (Node *) cstmt;
            pstmt->stmt_location = rs->stmt_location;
            pstmt->stmt_len = rs->stmt_len;

            /* Execute statement */
            ProcessUtility(pstmt,
                           cmd,
                           PROCESS_UTILITY_SUBCOMMAND, NULL,
                           NULL,
                           None_Receiver,
                           false,
                           NULL);

            /* Be sure to advance the command counter between subcommands */
            CommandCounterIncrement();

            callback_arg.tablename = NULL;
        }

        error_context_stack = sqlerrcontext.previous;
    }
}

/*
 * error context callback to let us supply the failing SQL statement's text
 */
static void
import_error_callback(void *arg)
{
    import_error_callback_arg *callback_arg = (import_error_callback_arg *) arg;
    int            syntaxerrposition;

    /* If it's a syntax error, convert to internal syntax error report */
    syntaxerrposition = geterrposition();
    if (syntaxerrposition > 0)
    {
        errposition(0);
        internalerrposition(syntaxerrposition);
        internalerrquery(callback_arg->cmd);
    }

    if (callback_arg->tablename)
        errcontext("importing foreign table \"%s\"",
                   callback_arg->tablename);
}
